home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / tcltk / tcl8.5 / tm.tcl < prev    next >
Encoding:
Text File  |  2009-11-22  |  11.3 KB  |  382 lines

  1. # -*- tcl -*-
  2. #
  3. # Searching for Tcl Modules. Defines a procedure, declares it as the
  4. # primary command for finding packages, however also uses the former
  5. # 'package unknown' command as a fallback.
  6. #
  7. # Locates all possible packages in a directory via a less restricted
  8. # glob. The targeted directory is derived from the name of the
  9. # requested package. I.e. the TM scan will look only at directories
  10. # which can contain the requested package. It will register all
  11. # packages it found in the directory so that future requests have a
  12. # higher chance of being fulfilled by the ifneeded database without
  13. # having to come to us again.
  14. #
  15. # We do not remember where we have been and simply rescan targeted
  16. # directories when invoked again. The reasoning is this:
  17. #
  18. # - The only way we get back to the same directory is if someone is
  19. #   trying to [package require] something that wasn't there on the
  20. #   first scan.
  21. #
  22. #   Either
  23. #   1) It is there now:  If we rescan, you get it; if not you don't.
  24. #
  25. #      This covers the possibility that the application asked for a
  26. #      package late, and the package was actually added to the
  27. #      installation after the application was started. It shoukld
  28. #      still be able to find it.
  29. #
  30. #   2) It still is not there: Either way, you don't get it, but the
  31. #      rescan takes time. This is however an error case and we dont't
  32. #      care that much about it
  33. #
  34. #   3) It was there the first time; but for some reason a "package
  35. #      forget" has been run, and "package" doesn't know about it
  36. #      anymore.
  37. #
  38. #      This can be an indication that the application wishes to reload
  39. #      some functionality. And should work as well.
  40. #
  41. # Note that this also strikes a balance between doing a glob targeting
  42. # a single package, and thus most likely requiring multiple globs of
  43. # the same directory when the application is asking for many packages,
  44. # and trying to glob for _everything_ in all subdirectories when
  45. # looking for a package, which comes with a heavy startup cost.
  46. #
  47. # We scan for regular packages only if no satisfying module was found.
  48.  
  49. namespace eval ::tcl::tm {
  50.     # Default paths. None yet.
  51.  
  52.     variable paths {/usr/share/tcltk/tcl8.5/tcl8}
  53.  
  54.     # The regex pattern a file name has to match to make it a Tcl Module.
  55.  
  56.     set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$}
  57.  
  58.     # Export the public API
  59.  
  60.     namespace export path
  61.     namespace ensemble create -command path -subcommand {add remove list}
  62. }
  63.  
  64. # ::tcl::tm::path implementations --
  65. #
  66. #    Public API to the module path. See specification.
  67. #
  68. # Arguments
  69. #    cmd -    The subcommand to execute
  70. #    args -    The paths to add/remove. Must not appear querying the
  71. #        path with 'list'.
  72. #
  73. # Results
  74. #    No result for subcommands 'add' and 'remove'. A list of paths
  75. #    for 'list'.
  76. #
  77. # Sideeffects
  78. #    The subcommands 'add' and 'remove' manipulate the list of
  79. #    paths to search for Tcl Modules. The subcommand 'list' has no
  80. #    sideeffects.
  81.  
  82. proc ::tcl::tm::add {path args} {
  83.     # PART OF THE ::tcl::tm::path ENSEMBLE
  84.     #
  85.     # The path is added at the head to the list of module paths.
  86.     #
  87.     # The command enforces the restriction that no path may be an
  88.     # ancestor directory of any other path on the list. If the new
  89.     # path violates this restriction an error wil be raised.
  90.     #
  91.     # If the path is already present as is no error will be raised and
  92.     # no action will be taken.
  93.  
  94.     variable paths
  95.  
  96.     # We use a copy of the path as source during validation, and
  97.     # extend it as well. Because we not only have to detect if the new
  98.     # paths are bogus with respect to the existing paths, but also
  99.     # between themselves. Otherwise we can still add bogus paths, by
  100.     # specifying them in a single call. This makes the use of the new
  101.     # paths simpler as well, a trivial assignment of the collected
  102.     # paths to the official state var.
  103.  
  104.     set newpaths $paths
  105.     foreach p [linsert $args 0 $path] {
  106.     if {$p in $newpaths} {
  107.         # Ignore a path already on the list.
  108.         continue
  109.     }
  110.  
  111.     # Search for paths which are subdirectories of the new one. If
  112.     # there are any then the new path violates the restriction
  113.     # about ancestors.
  114.  
  115.     set pos [lsearch -glob $newpaths ${p}/*]
  116.     # Cannot use "in", we need the position for the message.
  117.     if {$pos >= 0} {
  118.         return -code error \
  119.         "$p is ancestor of existing module path [lindex $newpaths $pos]."
  120.     }
  121.  
  122.     # Now look for existing paths which are ancestors of the new
  123.     # one. This reverse question forces us to loop over the
  124.     # existing paths, as each element is the pattern, not the new
  125.     # path :(
  126.  
  127.     foreach ep $newpaths {
  128.         if {[string match ${ep}/* $p]} {
  129.         return -code error \
  130.             "$p is subdirectory of existing module path $ep."
  131.         }
  132.     }
  133.  
  134.     set newpaths [linsert $newpaths 0 $p]
  135.     }
  136.  
  137.     # The validation of the input is complete and successful, and
  138.     # everything in newpaths is either an old path, or added. We can
  139.     # now extend the official list of paths, a simple assignment is
  140.     # sufficient.
  141.  
  142.     set paths $newpaths
  143.     return
  144. }
  145.  
  146. proc ::tcl::tm::remove {path args} {
  147.     # PART OF THE ::tcl::tm::path ENSEMBLE
  148.     #
  149.     # Removes the path from the list of module paths. The command is
  150.     # silently ignored if the path is not on the list.
  151.  
  152.     variable paths
  153.  
  154.     foreach p [linsert $args 0 $path] {
  155.     set pos [lsearch -exact $paths $p]
  156.     if {$pos >= 0} {
  157.         set paths [lreplace $paths $pos $pos]
  158.     }
  159.     }
  160. }
  161.  
  162. proc ::tcl::tm::list {} {
  163.     # PART OF THE ::tcl::tm::path ENSEMBLE
  164.  
  165.     variable paths
  166.     return  $paths
  167. }
  168.  
  169. # ::tcl::tm::UnknownHandler --
  170. #
  171. #    Unknown handler for Tcl Modules, i.e. packages in module form.
  172. #
  173. # Arguments
  174. #    original    - Original [package unknown] procedure.
  175. #    name        - Name of desired package.
  176. #    version        - Version of desired package. Can be the
  177. #              empty string.
  178. #    exact        - Either -exact or ommitted.
  179. #
  180. #    Name, version, and exact are used to determine
  181. #    satisfaction. The original is called iff no satisfaction was
  182. #    achieved. The name is also used to compute the directory to
  183. #    target in the search.
  184. #
  185. # Results
  186. #    None.
  187. #
  188. # Sideeffects
  189. #    May populate the package ifneeded database with additional
  190. #    provide scripts.
  191.  
  192. proc ::tcl::tm::UnknownHandler {original name args} {
  193.     # Import the list of paths to search for packages in module form.
  194.     # Import the pattern used to check package names in detail.  
  195.  
  196.     variable paths
  197.     variable pkgpattern
  198.  
  199.     # Without paths to search we can do nothing. (Except falling back
  200.     # to the regular search).
  201.  
  202.     if {[llength $paths]} {
  203.     set pkgpath [string map {:: /} $name]
  204.     set pkgroot [file dirname $pkgpath]
  205.     if {$pkgroot eq "."} {
  206.         set pkgroot ""
  207.     }
  208.  
  209.     # We don't remember a copy of the paths while looping. Tcl
  210.     # Modules are unable to change the list while we are searching
  211.     # for them. This also simplifies the loop, as we cannot get
  212.     # additional directories while iterating over the list. A
  213.     # simple foreach is sufficient.
  214.  
  215.     set satisfied 0
  216.     foreach path $paths {
  217.         if {![interp issafe] && ![file exists $path]} {
  218.         continue
  219.         }
  220.         set currentsearchpath [file join $path $pkgroot]
  221.         if {![interp issafe] && ![file exists $currentsearchpath]} {
  222.         continue
  223.         }
  224.         set strip [llength [file split $path]]
  225.  
  226.         # We can't use glob in safe interps, so enclose the following
  227.         # in a catch statement, where we get the module files out
  228.         # of the subdirectories. In other words, Tcl Modules are
  229.         # not-functional in such an interpreter. This is the same
  230.         # as for the command "tclPkgUnknown", i.e. the search for
  231.         # regular packages.
  232.  
  233.         catch {
  234.         # We always look for _all_ possible modules in the current
  235.         # path, to get the max result out of the glob.
  236.  
  237.         foreach file [glob -nocomplain -directory $currentsearchpath *.tm] {
  238.             set pkgfilename [join [lrange [file split $file] $strip end] ::]
  239.  
  240.             if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} {
  241.             # Ignore everything not matching our pattern
  242.             # for package names.
  243.             continue
  244.             }
  245.             if {[catch {package vcompare $pkgversion 0}]} {
  246.             # Ignore everything where the version part is
  247.             # not acceptable to "package vcompare".
  248.             continue
  249.             }
  250.  
  251.             # We have found a candidate, generate a "provide
  252.             # script" for it, and remember it.  Note that we
  253.             # are using ::list to do this; locally [list]
  254.             # means something else without the namespace
  255.             # specifier.
  256.  
  257.             # NOTE. When making changes to the format of the
  258.             # provide command generated below CHECK that the
  259.             # 'LOCATE' procedure in core file
  260.             # 'platform/shell.tcl' still understands it, or,
  261.             # if not, update its implementation appropriately.
  262.             #
  263.             # Right now LOCATE's implementation assumes that
  264.             # the path of the package file is the last element
  265.             # in the list.
  266.  
  267.             package ifneeded $pkgname $pkgversion \
  268.             "[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]"
  269.  
  270.             # We abort in this unknown handler only if we got
  271.             # a satisfying candidate for the requested
  272.             # package. Otherwise we still have to fallback to
  273.             # the regular package search to complete the
  274.             # processing.
  275.  
  276.             if {
  277.             ($pkgname eq $name) &&
  278.             [package vsatisfies $pkgversion {*}$args]
  279.             } then {
  280.             set satisfied 1
  281.             # We do not abort the loop, and keep adding
  282.             # provide scripts for every candidate in the
  283.             # directory, just remember to not fall back to
  284.             # the regular search anymore.
  285.             }
  286.         }
  287.         }
  288.     }
  289.  
  290.     if {$satisfied} {
  291.         return
  292.     }
  293.     }
  294.  
  295.     # Fallback to previous command, if existing.  See comment above
  296.     # about ::list...
  297.  
  298.     if {[llength $original]} {
  299.     uplevel 1 $original [::linsert $args 0 $name]
  300.     }
  301. }
  302.  
  303. # ::tcl::tm::Defaults --
  304. #
  305. #    Determines the default search paths.
  306. #
  307. # Arguments
  308. #    None
  309. #
  310. # Results
  311. #    None.
  312. #
  313. # Sideeffects
  314. #    May add paths to the list of defaults.
  315.  
  316. proc ::tcl::tm::Defaults {} {
  317.     global env tcl_platform
  318.  
  319.     lassign [split [info tclversion] .] major minor
  320.     set exe [file normalize [info nameofexecutable]]
  321.  
  322.     # Note that we're using [::list], not [list] because [list] means
  323.     # something other than [::list] in this namespace.
  324.     roots [::list \
  325.         [file dirname [info library]] \
  326.         [file join [file dirname [file dirname $exe]] lib] \
  327.         ]
  328.  
  329.     if {$tcl_platform(platform) eq "windows"} {
  330.     set sep ";"
  331.     } else {
  332.     set sep ":"
  333.     }
  334.     for {set n $minor} {$n >= 0} {incr n -1} {
  335.     foreach ev [::list \
  336.             TCL${major}.${n}_TM_PATH \
  337.             TCL${major}_${n}_TM_PATH \
  338.         ] {
  339.         if {![info exists env($ev)]} continue
  340.         foreach p [split $env($ev) $sep] {
  341.         path add $p
  342.         }
  343.     }
  344.     }
  345.     return
  346. }
  347.  
  348. # ::tcl::tm::roots --
  349. #
  350. #    Public API to the module path. See specification.
  351. #
  352. # Arguments
  353. #    paths -    List of 'root' paths to derive search paths from.
  354. #
  355. # Results
  356. #    No result.
  357. #
  358. # Sideeffects
  359. #    Calls 'path add' to paths to the list of module search paths.
  360.  
  361. proc ::tcl::tm::roots {paths} {
  362.     foreach {major minor} [split [info tclversion] .] break
  363.     foreach pa $paths {
  364.     set p [file join $pa tcl$major]
  365.     for {set n $minor} {$n >= 0} {incr n -1} {
  366.         set px [file join $p ${major}.${n}]
  367.         if {![interp issafe]} { set px [file normalize $px] }
  368.         path add $px
  369.     }
  370.     set px [file join $p site-tcl]
  371.     if {![interp issafe]} { set px [file normalize $px] }
  372.     path add $px
  373.     }
  374.     return
  375. }
  376.  
  377. # Initialization. Set up the default paths, then insert the new
  378. # handler into the chain.
  379.  
  380. if {![interp issafe]} { ::tcl::tm::Defaults }
  381. if {![interp issafe]} { ::tcl::tm::roots {/usr/lib/tcltk /usr/share/tcltk} }
  382.